﻿using System.Collections;
using System.Data;
using System;
using System.Text;
using System.Linq;

namespace Agile.DataAccess
{
	internal partial class DbCommand
	{
        public IDbCommand Parameter(string name, object value, DataTypes parameterType = DataTypes.Object, ParameterDirection direction = ParameterDirection.Input, int size = 0)
        {
            string script = this.Data.Sql.ToString();
            this.Data.Sql.Clear();
            string outStr;
            string newName = string.Format("{0}_{1}", "var", name);
            if (!this.ReplaceEx(script, "{" + name + "}", Data.Context.Data.FluentDataProvider.GetParameterName(newName), out outStr)) 
                newName = name;
            this.Data.Sql.Append(outStr);
             
            if (parameterType != DataTypes.Binary && !(value is byte[]) && ReflectionHelper.IsList(value))
                AddListParameterToInnerCommand(newName, value);
            else
                AddParameterToInnerCommand(newName, value, parameterType, direction, size);

            return this;
        }

		private int _currentIndex = 0;
		public IDbCommand Parameters(params object[] parameters)
		{
			if(parameters != null)
			{
				for (var i = 0; i < parameters.Count(); i++)
				{
					Parameter(_currentIndex.ToString(), parameters[_currentIndex]);
					_currentIndex++;
				}
			}
			return this;
		}

		private void AddListParameterToInnerCommand(string name, object value)
		{
			var list = (IEnumerable) value;
			var newInStatement = new StringBuilder();

			var k = -1;
			foreach (var item in list)
			{
				k++;
				if (k == 0)
                    newInStatement.Append("");
				else
					newInStatement.Append(",");
				
				var parameter = AddParameterToInnerCommand(name + "p" + k.ToString(), item);
				newInStatement.Append(parameter.ParameterName);
			}
            Data.Sql.Replace(Data.Context.Data.FluentDataProvider.GetParameterName(name), newInStatement.ToString());
		}

		private IDbDataParameter AddParameterToInnerCommand(string name, object value, DataTypes parameterType = DataTypes.Object, ParameterDirection direction = ParameterDirection.Input, int size = 0)
		{
			if (value == null)
				value = DBNull.Value;

			if (value.GetType().IsEnum)
				value = (int) value;

			var dbParameter = Data.InnerCommand.CreateParameter();
			if (parameterType == DataTypes.Object)
				dbParameter.DbType = (System.Data.DbType) Data.Context.Data.FluentDataProvider.GetDbTypeForClrType(value.GetType());
			else
				dbParameter.DbType = (System.Data.DbType) parameterType;
            //dbParameter.DbType = System.Data.DbType.AnsiString;
			dbParameter.ParameterName = Data.Context.Data.FluentDataProvider.GetParameterName(name);
			dbParameter.Direction = (System.Data.ParameterDirection) direction;
			dbParameter.Value = value;
			if (size > 0)
				dbParameter.Size = size;
			Data.InnerCommand.Parameters.Add(dbParameter);

			return dbParameter;
		}

		public IDbCommand ParameterOut(string name, DataTypes parameterType, int size)
		{
			if (!Data.Context.Data.FluentDataProvider.SupportsOutputParameters)
				throw new FluentDataException("The selected database does not support output parameters");
			Parameter(name, null, parameterType, ParameterDirection.Output, size);
			return this;
		}

		public TParameterType ParameterValue<TParameterType>(string outputParameterName)
		{
			outputParameterName = Data.Context.Data.FluentDataProvider.GetParameterName(outputParameterName);
			if (!Data.InnerCommand.Parameters.Contains(outputParameterName))
				throw new FluentDataException(string.Format("Parameter {0} not found", outputParameterName));

			var value = (Data.InnerCommand.Parameters[outputParameterName] as System.Data.IDataParameter).Value;

			if (value == DBNull.Value)
				return default(TParameterType);

			return (TParameterType)value;
		}

        private bool ReplaceEx(string original, string pattern, string replacement, out string outOriginal)
        {
            int count, position0, position1;
            count = position0 = position1 = 0;
            string upperString = original.ToUpper();
            string upperPattern = pattern.ToUpper();
            int inc = (original.Length / pattern.Length) * (replacement.Length - pattern.Length);
            char[] chars = new char[original.Length + Math.Max(0, inc)];
            while ((position1 = upperString.IndexOf(upperPattern, position0)) != -1)
            {
                for (int i = position0; i < position1; ++i) chars[count++] = original[i];
                for (int i = 0; i < replacement.Length; ++i) chars[count++] = replacement[i];
                position0 = position1 + pattern.Length;
            }
            if (position0 == 0)
            {
                outOriginal = original;
                return false;
            }
            for (int i = position0; i < original.Length; ++i) chars[count++] = original[i];
            outOriginal = new string(chars, 0, count);
            return true;

        }

	}
}
